import ccxt
from datetime import datetime, timedelta
from loguru import logger
import sys
from modules.errors import EET, ExchangeError
import threading




logger.remove()  # Remove default logger configuration
logger.add(sys.stdout, level="INFO",format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{message}</level>",colorize=True)  # Log to console
logger.add("logs/logfile.log", rotation="1 MB", level="DEBUG")  # Log to file with rotation

# bid - buy order
# ask - sell order

class Exchange():
    def __init__(self, api_settings: dict, trading_settings: dict, visual_settings: dict):
        self.exchange = ccxt.mexc(api_settings)
        
        # Инициализация, если чего-то нет, то будет дефолт значение
        self.symbol = trading_settings.get('symbol','GRIMACE/USDT')
        self.period = trading_settings.get('period', 1)
        self.percent = trading_settings.get('percent', 1)
        self.spread = trading_settings.get('spread', 2)
        self.precision = trading_settings.get('precision', 3)
        self.min_time = trading_settings.get('min_time', 10)
        self.amount = trading_settings.get('amount', 0.321)
        self.range = trading_settings.get('range', 0.5)
        self.delta = trading_settings.get('delta', 3)

        self.show_balance = visual_settings.get('show_balance', True)
        self.show_settings = visual_settings.get('show_settings', True)

        self.exchange.load_markets()
        self.market = self.exchange.market(self.symbol)

        if self.show_settings == True:
            logger.info("Trading settings:")
            logger.info(f"Symbol:    {self.symbol}")
            logger.info(f"Period:    {self.period}")
            logger.info(f"Percent:   {self.percent}")
            logger.info(f"Spread:    {self.spread}")
            logger.info(f"Precision: {self.precision}")
            logger.info(f"Min time:  {self.min_time}")
            logger.info(f"Amount:  {self.amount}")
            logger.info(f"Delta:  {self.delta}")
            logger.info("")
        
        if self.show_balance == True:
            self.get_balance()
        


    def __getattr__(self, name):
        if hasattr(self.exchange, name):
            return getattr(self.exchange, name)
        else:
            raise AttributeError(f"'Exchange' object has no attribute '{name}'")

    # absolute values (price, amount)
    def get_borders(self,symbol=''):
        if symbol == '':
            symbol=self.symbol
        try:
            order_book = self.exchange.fetch_order_book(symbol)
            highest_buy = max(order_book['bids'], key=lambda x: x[0])
            lowest_sell = min(order_book['asks'], key=lambda x: x[0])
            return (highest_buy, lowest_sell)
        except Exception as e:
            raise ExchangeError(EET.FETCH_ORDER_BOOK_ERROR, str(e))

    
    # in fractions, not %
    def check_gap(self,highest_buy,lowest_sell):
        #highest_buy, lowest_sell = self.get_borders(symbol)
        difference = lowest_sell[0]-highest_buy[0]
        return difference/lowest_sell[0]
    
    def get_balance(self):
        try:
            account_balance = self.exchange.fetch_balance()
            logger.info("API connection: SUCCESS")
            for currency, balance_info in account_balance['free'].items():
                if balance_info > 0:
                    logger.info(f"{currency:8}: {balance_info:.5f}".rstrip('0').rstrip('.'))
            logger.info("Trading settings:")

        except Exception as e:
            raise ExchangeError(EET.FETCH_BALANCE_ERROR, str(e))


    # in %  
    def get_change_minutes(self,symbol='',time=1):
        if symbol == '':
            symbol=self.symbol
        try:
            ohlcv_data = self.exchange.fetch_ohlcv(symbol, f'{time}m')

            current_time = datetime.now()
            start_time = current_time - timedelta(minutes=(time+1))

            for candle in ohlcv_data:
                timestamp = candle[0] / 1000
                candle_time = datetime.fromtimestamp(timestamp)
                if candle_time >= start_time:
                    start_price = candle[1]
                    break

            end_price = ohlcv_data[-1][4]
            price_change = end_price - start_price
            return (price_change/end_price)*100
        except Exception as e:
            raise ExchangeError(EET.FETCH_OHLCV_ERROR, str(e))
    
    def sell_order(self, market, amount, deal_price, margin_mode, query):
        sell_order = self.exchange.create_spot_order(market, 'limit', 'sell', amount, deal_price, margin_mode, query)
        return sell_order

    def buy_order(self, market, amount, deal_price, margin_mode, query):
        buy_order = self.exchange.create_spot_order(market, 'limit', 'buy', amount, deal_price, margin_mode, query)
        return buy_order
    
    def execute_threaded_orders(self, market, amount, deal_price, margin_mode, query):
        sell_thread = threading.Thread(target=self.sell_order, args=(market, amount, deal_price, margin_mode, query))
        buy_thread = threading.Thread(target=self.buy_order, args=(market, amount, deal_price, margin_mode, query))
        logger.info("START")
        sell_thread.start()
        buy_thread.start()
        logger.info("SENT")
        sell_thread.join()
        buy_thread.join()
        logger.info("ENDED")

    def check_history(self):
        # Calculate the start time for fetching trade history (1 hour ago)
        start_time = datetime.utcnow() - timedelta(minutes=15)
        start_time_unix = int(start_time.timestamp() * 1000)

        # Fetch trade history for the specified symbol since the start time
        trade_history = self.exchange.fetch_my_trades(self.symbol, since=start_time_unix)

        # Print the trade history
        for trade in trade_history:
            trade_type = trade['side']  # SELL or BUY
            trade_time = datetime.utcfromtimestamp(trade['timestamp'] / 1000)
            if start_time_unix<=int(trade_time.timestamp()*1000):
                print(f"{trade_type:5} | {trade_time.strftime('%Y-%m-%d %H:%M:%S')}")
    
    def check_change_n_range(self, symbol, timeframe):
        # Define the timeframe
        timeframe_str = f'{timeframe}m'    # Replace with your desired timeframe (e.g., '1m' for 1 minute candles)

        # Fetch historical candle data
        candle_data = self.exchange.fetch_ohlcv(symbol, timeframe_str, limit=2)

        # Extract high and low prices of the last two ticks
        if len(candle_data) >= 2:
            last_tick_high = candle_data[-1][2]  # High price of the last tick
            last_tick_low = candle_data[-1][3]   # Low price of the last tick
            second_last_tick_high = candle_data[-2][2]  # High price of the second last tick
            second_last_tick_low = candle_data[-2][3]   # Low price of the second last tick
            
            # Calculate the price range for the last two ticks in percentage
            range_high = max(last_tick_high, second_last_tick_high)
            range_low = min(last_tick_low, second_last_tick_low)
            price_range_percent = ((range_high - range_low) / range_low) * 100
            
            # Calculate the price change for the specified timeframe in percentage
            price_change_percent = self.get_change_minutes(symbol=symbol, time=timeframe)
            #print(price_change_percent, price_range_percent)
            return (price_change_percent, price_range_percent)
        else:
            logger.error("OHLCV MEXC ERROR")
            return 50,50

    def sell_buy(self, symbol='', amount=0, unchecked=False):
        if symbol == '':
            symbol=self.symbol

        if amount == 0:
            amount = self.amount

        try:
            # balance = self.fetch_balance()
            marginMode, query = self.exchange.handle_margin_mode_and_params('createOrder')

            if not unchecked:
                # check movement (NO PUMPS, DUMPS)
                change, rangee = self.check_change_n_range(symbol, self.period)
                good_movement = self.percent >= abs(change) and self.range>=abs(rangee)
            

            highest_buy, lowest_sell = self.get_borders(symbol)
            # check gap
            gap = self.check_gap(highest_buy,lowest_sell)
            good_gap = round(gap,self.precision) >= (self.spread/100)

            # other conditions
            #good_balance = 'USDT' in balance and balance['USDT']['free'] > 0
            logger.info(f"\33[32m{highest_buy[0]:.3f}\033[0m | {gap*100:.2f}% | \33[91m{lowest_sell[0]:.3f}\033[0m")
            #logger.info(f"Spread: {gap*100:.2f}%")
            if not unchecked:
                logger.info(f"{change:.3f}% change last {self.period}m")
                logger.info(f"{rangee:.3f}% range last {self.period}m")

            if unchecked or (good_gap and good_movement):
                PRECISION = self.precision # decimals after dot

                lowest_sell_price = lowest_sell[0]
                highest_buy_price = highest_buy[0]

                delta = self.delta*0.1**PRECISION # smallest change in price
                deal_price = round((highest_buy_price+lowest_sell_price)/2,PRECISION)
                #deal_price = round(lowest_sell_price-delta,PRECISION)
                logger.info(f"Deal price: {deal_price}")

                # try to use lower level functions
                """
                def create_limit_sell_order(self, symbol: str, amount, price, params={}):
                    return self.create_order(symbol, 'limit', 'sell', amount, price, params)

                def create_market_buy_order(self, symbol: str, amount, params={}):
                    return self.create_order(symbol, 'market', 'buy', amount, None, params)
                """
                
                self.execute_threaded_orders(self.market,amount,deal_price,margin_mode=marginMode,query=query)
                #logger.info("SELL")
                #sell_order = self.exchange.create_spot_order(self.market, 'limit', 'sell', amount, deal_price, marginMode, query)
                #sell_order = self.exchange.create_limit_sell_order(symbol, amount, deal_price)

                #marginMode, query = self.handle_margin_mode_and_params('createOrder')
                #logger.info("BUY")
                #buy_order = self.create_spot_order(self.market, 'market', 'buy', amount, deal_price, marginMode, query)
                #buy_order = self.exchange.create_limit_buy_order(symbol, amount, deal_price)

                #logger.debug(f"sell_order: {sell_order}")
                #logger.debug(f"buy_order: {buy_order}")

                # cancel all left-over orders just in case
                canceled_orders=self.exchange.cancel_all_orders(symbol)


                # after all - checking what happened
                #sell_info = self.exchange.fetch_order(sell_order['id'],symbol)
                #buy_info = self.exchange.fetch_order(buy_order['id'],symbol)
                #logger.debug(f"sell_info: {sell_info}")
                #logger.debug(f"buy_info: {buy_info}")
                if len(canceled_orders)==0:
                    logger.info("Both orders filled - SUCCESS")
                else:
                    logger.error("Some orders not filled, but now canceled (check logs file)")

                logger.debug(f"canceled_orders: {canceled_orders}")

                return None #sell_order, buy_order
            else:
                logger.error(EET.BAD_CONDITIONS.value)
                #raise ExchangeError(EET.INSUFFICIENT_FUNDS, "Insufficient funds for the operation")
        except Exception as e:
            raise ExchangeError(EET.SELL_BUY_ERROR, str(e))
